# -*- coding: utf-8 -*-
# standard
import os
import sys
import platform
import re
import time
import asyncio
from typing import Callable
from datetime import datetime, date
from functools import partial
from contextlib import asynccontextmanager
import copy

# third
import uvicorn
from fastapi import FastAPI
from fastapi import WebSocket
from fastapi.routing import APIRoute
from fastapi import Depends
from fastapi import Query as QueryParam
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import Response
from fastapi.responses import JSONResponse
from fastapi.responses import FileResponse
from fastapi.responses import RedirectResponse
from fastapi.responses import HTMLResponse
from fastapi.responses import RedirectResponse
from fastapi.requests import Request
from fastapi.exceptions import HTTPException
from fastapi.exceptions import StarletteHTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from fastapi.security import OAuth2PasswordRequestForm
from fastapi.openapi.docs import get_swagger_ui_html
from pydantic import BaseModel
from pydantic import Field

# local
_P_PATH = os.path.dirname(os.path.realpath(__file__))
if _P_PATH not in sys.path:
    sys.path.append(_P_PATH)
# 必须要加载的部分
from toolbox.common_tools import check_env, logger, get_fastapi_real_ip, INCLUDE_SCHEMA
from models import init_resources, cleanup_resources, start_init_default_thread
from views import import_views
from db.sqlalchemy_dbc import ENV


"""
@Title:  
@File: main_server.py
@Author: walle 2022年10日30日 23时13分01秒
@Version: 1.0.0
@Desc: 
"""


if platform.system() == "Windows":  # 如果是windows系统
    # 设置事件循环策略防止在客户端突然 断开时可能出现的出现 OSError: [WinError 64]
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())  


@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动时执行初始化
    # app 参数暂时没用上
    try:
        await init_resources()
        # 初始化其他服务（如Redis、消息队列）
        yield  # 初始化完成，返回
    except Exception as e:
        logger.error(f"初始化失败: {e}")
        logger.exception(e)
        # 关闭时执行清理
        await cleanup_resources()


if ENV != "dev": # 非开发环境 关闭文档
    # docs_url=None 表示启用自定义docs文档 
    app = FastAPI(docs_url=None, redoc_url=None, lifespan=lifespan) # 初始化fastapi app lifespan 用来注册生命周期函数
else:  # 开发环境 开启文档， 其中docs_url=None 表示启用自定义docs文档
    app = FastAPI(docs_url=None, redoc_url="/redos", lifespan=lifespan) # 初始化fastapi app lifespan 用来注册生命周期函数
app.mount("/static", StaticFiles(directory="static"), name="static")
import_views(app=app)



PORT = 12007
origins = ['*']  # debug模式允许所有的主机访问
app.add_middleware(
    CORSMiddleware,  # 跨域的中间件
    allow_origins=origins,  # 允许跨域的网址列表
    allow_credentials=True,  # 允许跨域的 https
    allow_methods=["*"],  # 允许跨域的方法
    allow_headers=["*"],  # 允许跨域的请求头信息
)



@app.get("/docs", include_in_schema=False)  # include_in_schema=False 不出现在自动文档中.
async def custom_swagger_ui_html(
    uid: str = QueryParam(None, title="当前日期", description="当前日期"),
):
    """
    自定义 swagger_ui的文档路由
    用于解决 swagger-ui-bundle.js 和  swagger-ui.css 有时候因为网络受限无法访问的问题
    swagger_ui 仓库地址: https://github.com/swagger-api/swagger-ui
    """
    today = date.today().strftime("%Y%m%d")
    if ENV != "dev" and uid != today:
        logger.warning(f"ENV = {ENV}, uid = {uid}, today = {today}")
        raise HTTPException(status_code=404, detail="not found")
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title=app.title + " - Swagger UI",
        oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
        swagger_js_url="/static/js/swagger-ui-bundle.js",
        swagger_css_url="/static/css/swagger-ui.css",
    )


#  一个处理长耗时任务的API接口的设计Demo


@app.get("/tasks", summary="接受长耗时任务请求", include_in_schema=False)
async def read_tasks():
    # resp = RedirectResponse(status_code=302, url="/status")  # 方案1 自动跳转到状态接口
    resp = Response(status_code=202, content="Resource creation accepted.")  # 方案2 客户端手动访问状态接口
    return resp


@app.get("/status", summary="状态任务检查", include_in_schema=False)
async def read_status():
    status = ...
    if status == "处理中":
        # link 提供的是取消任务的操作方法.
        payload = {"status": "处理中", "link": {"rel": "cancel", "method": "post", "href": "/cancel"}}
        return JSONResponse(content=payload, status_code=202)
    elif status == "已取消":
        return {"status": "已取消"}
    else:  # 处理完成
        return RedirectResponse(status_code=302, url="/result")


@app.post("/cancel", summary="取消任务", include_in_schema=False)
async def cancel_task():
    return {"status": "已取消"}


@app.get("/result", summary="返回任务的结果", include_in_schema=False)
async def read_result():
    return {"result": "result"}


###############################


# 自定义 404 异常处理器  
@app.exception_handler(StarletteHTTPException)  
async def http_exception_handler(request: Request, exc: StarletteHTTPException):  
    if exc.status_code == 404:  
        client_ip = request.client.host  
        request_url = str(request.url)  
        error_time = datetime.now().isoformat()  
        # 记录日志  
        logger.error(f"404 Not Found - URL: {request_url}, IP: {client_ip}, Time: {error_time}")  
        return JSONResponse(
            status_code=exc.status_code,
            content={"detail": exc.detail}
        )
        # 你可以自定义返回给客户端的响应  
        # return JSONResponse(  
        #     status_code=exc.status_code,  
        #     content={"detail": exc.detail, "url": request_url, "ip": client_ip, "time": error_time}  
        # )  
    else:  
        return JSONResponse(  
            status_code=exc.status_code,  
            content={"detail": exc.detail}  
        )  


@app.middleware(middleware_type="http")
async def middleware_example(request: Request, call_next: Callable):
    """
    这是一个中间件，当前的设计使其相当于 flask的 before_request 和 after_request 之类的函数
    :param request: 请求体
    :param call_next: 原来的视图函数
    :return:
    """
    if 'jndi:' in request.url._url:  # 注入
        logger.warning(f"发现可疑请求: {request.url._url} ,ip: {get_fastapi_real_ip(request)}")
        return Response(status_code=403, content="Hacker behavior has been recorded")
    elif 'eval(' in request.url._url:  # 注入
        logger.warning(f"发现可疑请求: {request.url._url} ,ip: {get_fastapi_real_ip(request)}")
        return Response(status_code=403, content="Hacker behavior has been recorded")
    # 放行 
    t1 = time.perf_counter()
    resp = await call_next(request)
    # log_resp_status = resp.status_code
    t2 = time.perf_counter()
    d = round(t2 - t1, 6)
    if d > 0.5:
        logger.warning(f"发现长耗时请求: 路径: {request.url._url}, ip: {get_fastapi_real_ip(request)}, 请求耗时: {d}秒")  
    else:
        logger.info(f"请求路径: {request.url._url}, ip: {get_fastapi_real_ip(request)}, 响应耗时: {d}秒")  
    return resp


if __name__ == '__main__':
    import colorama
    color = colorama.Fore.CYAN
    print(color + "打印全部路由")
    for x in app.routes:
        logger.debug(color + getattr(x, "path").strip())
    logger.debug(color + "打印全部路由结束")
    print(colorama.Style.RESET_ALL)  # 重置颜色
    logger.info(f"当前工作环境: {ENV}")
    reload = False  # 是否启用热重载
    if platform.system() == "Windows":
        workers = 1
        reload = True  # 开发环境下启用热重载
    elif ENV == "uat":
        workers = 1
    else:
        workers = 3
    logger.debug(f"worker = {workers}")
    start_init_default_thread()  # 初始化数据库默认值
    # 以编码方式启动服务
    
    uvicorn.run(app="main_server:app", port=PORT, host="0.0.0.0", workers=workers, reload=reload, log_level="info")
    pass